/**
 * $Id$ 
 *
 * Copyright 2012 zhaigy hontlong@gmail.com
 */

#include "./config.h"

using namespace std;

// Construct a Config, getting keys and values from given file
Config::Config(string filename, string delimiter, string comment)
    : m_Delimiter(delimiter), m_Comment(comment) {
      std::ifstream in(filename.c_str());
      if (!in) throw File_not_found(filename);
      in >> (*this);
    }

Config::Config() : m_Delimiter(string(1, '=')), m_Comment(string(1, '#')) {
}

bool Config::KeyExists(const string& key) const {
  mapci p = m_Contents.find(key);
  return p != m_Contents.end();
}

/* static */
void Config::Trim(string& inout_s) {
  static const char whitespace[] = " \n\t\v\r\f";
  inout_s.erase(0, inout_s.find_first_not_of(whitespace));
  inout_s.erase(inout_s.find_last_not_of(whitespace) + 1U);
}

std::ostream& operator<<(std::ostream& os, const Config& cf) {
  // Save a Config to os
  for (Config::mapci p = cf.m_Contents.begin(); p != cf.m_Contents.end(); ++p) {
    os << p->first << " " << cf.m_Delimiter << " ";
    os << p->second << std::endl;
  }
  return os;
}

void Config::Remove(const string& key) {
  // Remove key and its value
  m_Contents.erase(m_Contents.find(key));
  return;
}

std::istream& operator>>(std::istream& is, Config& cf) {
  // Load a Config from is
  // Read in keys and values, keeping internal whitespace
  typedef string::size_type pos;
  const string& delim = cf.m_Delimiter;  // separator
  const string& comm = cf.m_Comment;  // comment
  const pos skip = delim.length();  // length of separator

  string nextline = "";  // might need to read ahead to see where value ends

  while (is || nextline.length() > 0) {
    // Read an entire line at a time
    string line;
    if (nextline.length() > 0) {
      line = nextline;  // we read ahead; use it now
      nextline = "";
    } else {
      std::getline(is, line);
    }

    // Ignore comments
    line = line.substr(0, line.find(comm));

    // Parse the line if it contains a delimiter
    pos delimPos = line.find(delim);
    if (delimPos < string::npos) {
      // Extract the key
      string key = line.substr(0, delimPos);
      line.replace(0, delimPos + skip, "");

      // See if value continues on the next line
      // Stop at blank line, next line with a key, end of stream,
      // or end of file sentry
      bool terminate = false;
      while (!terminate && is) {
        std::getline(is, nextline);
        terminate = true;

        string nlcopy = nextline;
        Config::Trim(nlcopy);
        if (nlcopy == "") continue;

        nextline = nextline.substr(0, nextline.find(comm));
        if (nextline.find(delim) != string::npos) continue;

        nlcopy = nextline;
        Config::Trim(nlcopy);
        if (nlcopy != "") line += "\n";
        line += nextline;
        terminate = false;
      }

      // Store key and value
      Config::Trim(key);
      Config::Trim(line);
      cf.m_Contents[key] = line;  // overwrites if key is repeated
    }
  }

  return is;
}

bool Config::FileExist(std::string filename) {
  bool exist = false;
  std::ifstream in(filename.c_str());
  if (in) exist = true;
  return exist;
}

void Config::ReadFile(string filename, string delimiter, string comment) {
  m_Delimiter = delimiter;
  m_Comment = comment;
  std::ifstream in(filename.c_str());

  if (!in) throw File_not_found(filename);
  in >> (*this);
}

